package org.example.netty.test.nio.java;

import lombok.extern.slf4j.Slf4j;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.StandardCharsets;

import static org.example.netty.test.nio.util.ByteBufferUtil.debugAll;

/**
 * @author zp
 * @version 1.0
 * @description: 非阻塞IO 的测试类
 * <p>
 * 一、JavaIO类库是阻塞式IO；从1.4版本开始，引进了新的异步IO库，被称为Java New IO类库，简称为Java NIO。
 * 二、Java NIO类库包含以下三个核心组件：
 * <p>
 * Channel（通道）
 * Channel和Stream的一个显著的不同是；
 * Channel是双向的，既可以用来进行读操作，又可以用来进行写操作
 * Stream是单向的，譬如InputStream是单向的只读流，OutputStream是单向的只写流；
 * NIO中的Channel的主要实现有：
 * 1.FileChannel 用于文件IO操作
 * 2.DatagramChannel 用于UDP的IO操作
 * 3.SocketChannel 用于TCP的传输操作
 * 4.ServerSocketChannel 用于TCP连接监听操作
 * Buffer（缓冲区）非线程安全类
 * 1、应用程序与通道（Channel）主要的交互，主要是进行数据的read读取和write写入。为了完成NIO的非阻塞读写操作，NIO为大家准备了第三个重要的组件——NIO Buffer（NIO缓冲区）。
 * <p>
 * 2、Buffer顾名思义：缓冲区，实际上是一个容器，一个连续数组。Channel提供从文件、网络读取数据的渠道，但是读写的数据都必须经过Buffer。
 * <p>
 * Selector（选择器）
 * 1、IO多路复用指的是一个进程/线程可以同时监视多个文件描述符（含socket连接），
 * 一旦其中的一个或者多个文件描述符可读或者可写，该监听进程/线程能够进行IO事件的查询。
 * <p>
 * 2、Selector 选择器。Selector选择器可以理解为一个IO事件的监听与查询器。通过选择器，一个线程可以查询多个通道的IO事件的就绪状态。
 * <p>
 * 3、Java NIO将NIO事件进行了简化，只定义了四个事件，这四种事件用SelectionKey的四个常量来表示：
 * （1）SelectionKey.OP_CONNECT
 * （2）SelectionKey.OP_ACCEPT
 * （3）SelectionKey.OP_READ
 * （4）SelectionKey.OP_WRITE
 * <p>
 * 三、NIO和OIO的对比
 * 在Java中，NIO和OIO的区别，主要体现在三个方面：
 * <p>
 * （1）OIO是面向流（Stream Oriented）的，NIO是面向缓冲区（Buffer Oriented）的。
 * <p>
 * 问题是：什么是面向流，什么是面向缓冲区呢？
 * <p>
 * 在面向流的OIO操作中，IO的 read() 操作总是以流式的方式顺序地从一个流（Stream）中读取一个或多个字节，因此，我们不能随意地改变读取指针的位置，也不能前后移动流中的数据。
 * <p>
 * 而NIO中引入了Channel（通道）和Buffer（缓冲区）的概念。面向缓冲区的读取和写入，都是与Buffer进行交互。用户程序只需要从通道中读取数据到缓冲区中，或将数据从缓冲区中写入到通道中。NIO不像OIO那样是顺序操作，可以随意地读取Buffer中任意位置的数据，可以随意修改Buffer中任意位置的数据。
 * <p>
 * （2）OIO的操作是阻塞的，而NIO的操作是非阻塞的。
 * <p>
 * OIO的操作是阻塞的，当一个线程调用read() 或 write()时，该线程被阻塞，直到有一些数据被读取，或数据完全写入。该线程在此期间不能再干任何事情了。例如，我们调用一个read方法读取一个文件的内容，那么调用read的线程会被阻塞住，直到read操作完成。
 * <p>
 * NIO如何做到非阻塞的呢？当我们调用read方法时，系统底层已经把数据准备好了，应用程序只需要从通道把数据复制到Buffer（缓冲区）就行；如果没有数据，当前线程可以去干别的事情，不需要进行阻塞等待。
 * <p>
 * NIO的非阻塞是如何做到的呢？
 * <p>
 * 其实在上一章，答案已经揭晓了，根本原因是：NIO使用了通道和通道的IO多路复用技术。
 * <p>
 * （3）OIO没有选择器（Selector）概念，而NIO有选择器的概念。
 * <p>
 * NIO技术的实现，是基于底层的IO多路复用技术实现的，比如在Windows中需要select多路复用组件的支持，在Linux系统中需要select/poll/epoll多路复用组件的支持。所以NIO的需要底层操作系统提供支持。而OIO不需要用到选择器。
 * @date 2023/2/19 13:53
 */
@Slf4j
public class NioDemo {
    public static void main(String[] args) {

        // testUseBuffer();
        // buildBytebuffer();
        // segmentedWrites();
        // centralizedWrite();
        stickyAndHalfPack();
    }

    /**
     * @description:  拆解半包和粘包的数据
     * @author zp
     * @date: 2023/2/25 16:52
     */
    private static void stickyAndHalfPack() {
        ByteBuffer buffer = ByteBuffer.allocate(32);
        buffer.put("hello,world\nI'm zhangsan\nHo".getBytes());
        split(buffer);
        buffer.put("w are you?\n".getBytes());
        split(buffer);
    }

    /**
     * @description: 拆解数据的方法
     * @author zp
     * @date: 2023/2/25 16:54
     * @param buffer
     */
    private static void split(ByteBuffer buffer) {
        buffer.flip();
        for (int i = 0; i < buffer.limit(); i++) {
            if (buffer.get(i) == '\n') {
                int length = i + 1 - buffer.position();
                ByteBuffer byteBuffer = ByteBuffer.allocate(length);
                for (int j = 0; j < length; j++) {
                    byteBuffer.put(buffer.get());
                }
                debugAll(byteBuffer);
            }
        }
        buffer.compact();
    }

    /**
     * @description: 集中写入 然后按照位数获取单独的数据
     * @author zp
     * @date: 2023/2/25 16:17
     */
    private static void centralizedWrite() throws IOException {
        File file = createFile("E:\\IdeaProjects\\myWorkspace\\NettyTest\\src\\main\\resources\\hello.txt");
        FileInputStream fileInputStream = new FileInputStream(file);
        FileChannel fileInChannel = fileInputStream.getChannel();

        ByteBuffer context = ByteBuffer.allocate(11);

        fileInChannel.read(context);
        context.flip();
        byte[] bytes = new byte[3];
        byte[] byteList = new byte[5];
        context.get(bytes);
        debugAll(context);
        log.debug("打印的内容[{}]", new String(bytes, StandardCharsets.UTF_8));
        context.get(bytes);
        debugAll(context);
        log.debug("打印的内容[{}]", new String(bytes, StandardCharsets.UTF_8));
        context.get(byteList);
        debugAll(context);
        log.debug("打印的内容[{}]", new String(byteList, StandardCharsets.UTF_8));
    }

    /**
     * @description: 分段写入信息 然后各自封装到ByteBuffer
     * @author zp
     * @date: 2023/2/25 16:10
     */
    private static void segmentedWrites() throws IOException {
        File file = createFile("E:\\IdeaProjects\\myWorkspace\\NettyTest\\src\\main\\resources\\hello.txt");
        FileInputStream fileInputStream = new FileInputStream(file);
        FileChannel fileInChannel = fileInputStream.getChannel();

        ByteBuffer one = ByteBuffer.allocate(3);
        ByteBuffer two = ByteBuffer.allocate(3);
        ByteBuffer three = ByteBuffer.allocate(5);

        fileInChannel.read(new ByteBuffer[]{one, two, three});
        one.flip();
        two.flip();
        three.flip();

        debugAll(one);
        debugAll(two);
        debugAll(three);
    }

    private static File createFile(String filePath) throws IOException {
        File file = new File(filePath);
        if (!file.exists()) {
            file.createNewFile();
            FileOutputStream fileOutputStream = new FileOutputStream(file);
            fileOutputStream.write("onetwothree".getBytes());
            fileOutputStream.flush();
            fileOutputStream.close();
        }
        return file;
    }

    /**
     * @description: 测试ByteBuffer 当中每个方法的作用和标志位的变化
     * @author zp
     * @date: 2023/2/25 15:56
     */
    private static void testUseBuffer() {
        UseBuffer.allocateTest(); // 获取缓冲器里面的数值
        UseBuffer.putTest(); // 测试数据插入 缓冲器值的变化
        UseBuffer.flipTest(); // 测试数据缓冲器 写读翻转 缓冲器值的变化
        UseBuffer.getTest(); // 测试从缓冲区读取数据 查看缓冲区数值
        UseBuffer.rewindTest(); // 测试倒带 查看缓冲区数值
        UseBuffer.reRead(); // 测试倒带之后 重新读取数据 查看缓冲区数值
        UseBuffer.afterReset(); // 测试还原之后的缓冲区 查看缓冲区数值
        UseBuffer.clearTest(); // 测试清除缓冲区 读写翻转
    }

    /**
     * @description: 字符串生成ByteBuffer
     * @author zp
     * @date: 2023/2/25 15:57
     */
    private static void buildBytebuffer() {
        // 字符串转换 ByteBuffer
        String hello = "hello";
        // 使用put方法 把字符串转成字节数组 放入 不会自动转换读模式
        ByteBuffer allocate = ByteBuffer.allocate(16);
        allocate.put(hello.getBytes());
        debugAll(allocate);

        // 使用ByteBuffer.wrap的方法 也可以生成一个ByteBuffer
        // ByteBuffer 转换之后 自动是读模式 而且 byteBuffer的长度就等于 字符串转换的字节数组长度
        ByteBuffer wrap = ByteBuffer.wrap(hello.getBytes());
        debugAll(wrap);

        // 使用Charset 的encode方法转换 ByteBuffer 转换之后 自动是读模式
        // 而且 byteBuffer的长度就等于 字符串转换的字节数组长度
        ByteBuffer byteBuffer = StandardCharsets.UTF_8.encode(hello);
        debugAll(byteBuffer);
    }
}
